package adventure.commands.skills;

import adventure.Adventure;
import adventure.commands.Util;
import adventure.placeable.Fightable;
import adventure.placeable.Mobile;
import colour.Colour;
import colour.ColourUtil;
import player.Player;

/**
 *
 * @author Michael Hanns
 *
 */
public class SingleTargetDamage extends AbstractSkill {

    protected final static String type = "DAMAGETARGET";
    protected final static Colour typeColour = ColourUtil.darkYellow;
    private Colour[] criticalHit = ColourUtil.criticalHit;

    public SingleTargetDamage(Adventure a, String[] inputSynonyms, int id) {
        super(a, inputSynonyms, id);
    }

    public SingleTargetDamage(Adventure a, int id) {
        super(a, new String[]{""}, id);
    }

    @Override
    public String getType() {
        return type;
    }

    @Override
    public Colour getTypeColour() {
        return typeColour;
    }

    @Override
    protected String performSkill(Player player, String[] args) {
        if (args.length == 1) {
            if (player.inCombat()) {
                Fightable[] targets = player.getLocalTargets();

                if (targets[0].isPlayer()) {
                    return performSkillOnPlayer(player, (Player) targets[0]);
                } else {
                    return performSkillOnMobile(player, (Mobile) targets[0]);
                }
            } else {
                return ColourUtil.colourise(
                        synonyms[0].toUpperCase().substring(0, 1)
                        + synonyms[0].toLowerCase().substring(1), typeColour)
                        + " what?";
            }
        }

        String target = Util.consolidateArguments(args);
        int x = player.x();
        int y = player.y();
        int z = player.z();

        Mobile mob = rooms.getRoomMobile(x, y, z, target);
        if (mob.getID() > 0) {
            return performSkillOnMobile(player, mob);
        } else {
            Player targetPlayer = players.getOnlinePlayer(rooms.getRoomPlayer(x, y, z, target));
            if (targetPlayer.getID() > 0) {
                return performSkillOnPlayer(player, targetPlayer);
            } else {
                return "You don't see any " + target + "!";
            }
        }
    }

    private String performSkillOnMobile(Player player, Mobile mob) {
        int x = player.x();
        int y = player.y();
        int z = player.z();

        String output = "";

        if (player.attack((Fightable) mob)) {
            output += "\nYou attack "
                    + ColourUtil.colourise(mob.getAliveName(), ColourUtil.mobile) + "!\n";

            out.broadcastLocalToPlayer(player,
                    ColourUtil.colourise(player.getName(), ColourUtil.player)
                    + " attacks "
                    + ColourUtil.colourise(mob.getAliveName(), ColourUtil.mobile) + "!");
        }


        output += dealMobileDamage(player, mob);

        consumePrerequisites(player);

        return output;
    }

    private String performSkillOnPlayer(Player player, Player targetPlayer) {
        String output = "";

        if (targetPlayer.pvpImmune()) {
            return "\nYou cannot attack another player so recently slain.";
        } else {
            String target = targetPlayer.getName();
            if (player.attack((Fightable) targetPlayer)) {
                out.broadcastToPlayer(targetPlayer,
                        ColourUtil.colourise(player.getName(), ColourUtil.player)
                        + " has attacked you!");

                output += "\nYou attack "
                        + ColourUtil.colourise(target, ColourUtil.player) + "!\n";

                out.broadcastLocalToPlayers(new Player[]{player, targetPlayer},
                        ColourUtil.colourise(player.getName(), ColourUtil.player)
                        + " attacks "
                        + ColourUtil.colourise(target, ColourUtil.player) + "!");
            }
        }


        output += dealPlayerDamage(player, targetPlayer);

        consumePrerequisites(player);

        return output;
    }

    private String dealMobileDamage(Player attacker, Mobile defender) {
        if (sameLocation((Fightable) attacker, (Fightable) defender)
                && stillAlive((Fightable) attacker, (Fightable) defender)) {

            boolean skillSuccess[] = skillSuccess(attacker);

            if (!skillSuccess[0]) {
                return ColourUtil.colourise(
                        synonyms[0].toUpperCase().substring(0, 1)
                        + synonyms[0].toLowerCase().substring(1), typeColour) + " failed!";
            }

            double critMod = attackerCrit((Fightable) attacker, (Fightable) defender, skillSuccess[1]);
            boolean evadeSuccess = false;

            if (critMod == 1) {
                evadeSuccess = defenderEvade((Fightable) attacker, (Fightable) defender);
            }

            if (evadeSuccess) {
                out.broadcastLocalToPlayer(attacker,
                        ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile)
                        + " has evaded "
                        + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player)
                        + "'s attack!");

                return ColourUtil.colourise(defender.getAliveName(), ColourUtil.red)
                        + " has evaded your " + ColourUtil.colourise(synonyms[0].toLowerCase(), typeColour) + "!";

            }

            int dmgRcvd = dealDamage((Fightable) attacker, critMod, (Fightable) defender);

            String attackoutput = generateAttackOutput((Fightable) attacker, false, (Fightable) defender, true, dmgRcvd, 0);
            String observeroutput = generateAttackOutput((Fightable) attacker, false, (Fightable) defender, true, dmgRcvd, 2);

            if (critMod > 1) {
                attackoutput = criticalHit + attackoutput;
                observeroutput = criticalHit + observeroutput;
            }


            if (defender.isDead()) {
                attackoutput += "\nYou have slain "
                        + ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile)
                        + "!";
                observeroutput += "\n"
                        + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player)
                        + " has slain "
                        + ColourUtil.colourise(defender.getAliveName(), ColourUtil.mobile)
                        + "!";

                // Players gain experience from combat!
                // Get level difference
                int xpGained = getXPReward((Fightable) attacker, (Fightable) defender); // Gain standard
                attackoutput = attackoutput + "\n" + ((Player) attacker).gainXP(xpGained, false);

                // Lucky players get bonus experience!
                xpGained = getBonusXPReward((Fightable) attacker, xpGained, (Fightable) defender);
                if (xpGained > 0) {
                    attackoutput = attackoutput + "\n" + ((Player) attacker).gainXP(xpGained, true);
                }

                attacker.stopFighting((Fightable) defender);
                defender.stopFighting(attacker);
            }

            out.broadcastLocalToPlayer(attacker, observeroutput);

            return attackoutput;


        } else {
            attacker.stopFighting((Fightable) defender);
            defender.stopFighting(attacker);
        }
        return "";
    }

    private String dealPlayerDamage(Player attacker, Player defender) {
        if (sameLocation((Fightable) attacker, (Fightable) defender)
                && stillAlive((Fightable) attacker, (Fightable) defender)) {
            boolean skillSuccess[] = skillSuccess(attacker);

            if (!skillSuccess[0]) {
                return ColourUtil.colourise(
                        synonyms[0].toUpperCase().substring(0, 1)
                        + synonyms[0].toLowerCase().substring(1), typeColour) + " failed!";
            }

            double critMod = attackerCrit((Fightable) attacker, (Fightable) defender, skillSuccess[1]);
            boolean evadeSuccess = false;

            if (critMod == 1) {
                evadeSuccess = defenderEvade((Fightable) attacker, (Fightable) defender);
            }

            if (evadeSuccess) {
                String attackoutput =
                        ColourUtil.colourise(defender.getAliveName(), ColourUtil.player)
                        + " has evaded your " + ColourUtil.colourise(synonyms[0].toLowerCase(), typeColour) + "!";
                String defendoutput = "You have evaded "
                        + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player)
                        + "'s " + ColourUtil.colourise(synonyms[0].toLowerCase(), typeColour) + "!";
                String observeroutput =
                        ColourUtil.colourise(defender.getAliveName(), ColourUtil.player)
                        + " has evaded "
                        + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player)
                        + "'s " + ColourUtil.colourise(synonyms[0].toLowerCase(), typeColour) + "!";

                out.broadcastToPlayer(defender, defendoutput);
                out.broadcastLocalToPlayers(new Player[]{attacker, defender},
                        observeroutput);

                return attackoutput;
            }

            int dmgRcvd = dealDamage((Fightable) attacker, critMod, (Fightable) defender);
            System.out.println("Damage generated.");

            String attackoutput = generateAttackOutput((Fightable) attacker, false, (Fightable) defender, false, dmgRcvd, 0);
            String defendoutput = generateAttackOutput((Fightable) attacker, false, (Fightable) defender, false, dmgRcvd, 1);
            String observeroutput = generateAttackOutput((Fightable) attacker, false, (Fightable) defender, false, dmgRcvd, 2);


            if (critMod > 1) {
                attackoutput = criticalHit + attackoutput;
                defendoutput = criticalHit + defendoutput;
                observeroutput = criticalHit + observeroutput;
            }

            if (defender.isDead()) {
                attackoutput += "\nYou have slain " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + "!";
                defendoutput += "\n" + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " has slain you!\nYou are dead! Type RESPAWN to respawn to your closest checkpoint.";
                observeroutput += "\n" + ColourUtil.colourise(attacker.getAliveName(), ColourUtil.player) + " has slain " + ColourUtil.colourise(defender.getAliveName(), ColourUtil.player) + "!";

                // Players gain experience from combat!
                // Get level difference
                int xpGained = getXPReward((Fightable) attacker, (Fightable) defender); // Gain standard
                attackoutput += "\n" + ((Player) attacker).gainXP(xpGained, false);

                // Lucky players get bonus experience!
                xpGained = getBonusXPReward((Fightable) attacker, xpGained, (Fightable) defender);
                if (xpGained > 0) {
                    attackoutput += "\n" + ((Player) attacker).gainXP(xpGained, true);
                }

                attacker.stopFighting((Fightable) defender);
                defender.stopFighting((Fightable) attacker);
            }

            out.broadcastToPlayer(defender, defendoutput);
            out.broadcastLocalToPlayers(new Player[]{attacker, defender},
                    observeroutput);

            return attackoutput;


        } else {
            attacker.stopFighting((Fightable) defender);
            defender.stopFighting((Fightable) attacker);
        }

        return "";
    }

    /*
     * Generates attack output for Player vs Player SingleTargetDamage skill.
     * 
     * @param forWhom - tells the method who to generate the output for. 0 is
     *                  the attacking player, 1 is the defending player,
     *                  2 is all 3rd party observers.
     */
    private String generateAttackOutput(Fightable player, boolean mob1,
            Fightable target, boolean mob2, int dmg, int forWhom) {

        Colour colourPlayer;
        Colour colourTarget;

        if (mob1) {
            colourPlayer = ColourUtil.mobile;
        } else {
            colourPlayer = ColourUtil.player;
        }
        if (mob2) {
            colourTarget = ColourUtil.mobile;
        } else {
            colourTarget = ColourUtil.player;
        }

        if (actionFlag == YOU) {
            if (forWhom == 2) {
                return ColourUtil.colourise(player.getAliveName(), colourPlayer) + " "
                        + ColourUtil.colourise(actionDesc, typeColour) + " "
                        + ColourUtil.colourise(target.getAliveName(), colourTarget)
                        + ColourUtil.colourise(" for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            } else if (forWhom == 1) {
                return ColourUtil.colourise(player.getAliveName(), colourPlayer) + " "
                        + ColourUtil.colourise(actionDesc, typeColour)
                        + ColourUtil.colourise(" you for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            } else if (forWhom == 0) {
                return ColourUtil.colourise("You " + actionDesc, typeColour) + " "
                        + ColourUtil.colourise(target.getAliveName(), colourTarget)
                        + ColourUtil.colourise(" for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            }
        } else if (actionFlag == YOUR) {
            if (forWhom == 2) {
                return ColourUtil.colourise(player.getAliveName() + "'s ", colourPlayer)
                        + ColourUtil.colourise(actionDesc, typeColour) + " "
                        + ColourUtil.colourise(target.getAliveName(), colourTarget)
                        + ColourUtil.colourise(" for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            } else if (forWhom == 1) {
                return ColourUtil.colourise(player.getAliveName() + "'s ", colourPlayer)
                        + ColourUtil.colourise(actionDesc, typeColour)
                        + ColourUtil.colourise(" you for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            } else if (forWhom == 0) {
                return ColourUtil.colourise("Your " + actionDesc, typeColour) + " "
                        + ColourUtil.colourise(target.getAliveName(), colourTarget)
                        + ColourUtil.colourise(" for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            }
        } else if (actionFlag == CUSTOM) {
            if (forWhom == 2) {
                return replaceActionKeywords(((Player) player)) + " "
                        + ColourUtil.colourise(target.getAliveName(), colourTarget)
                        + ColourUtil.colourise(" for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            } else if (forWhom == 1) {
                return replaceActionObserverKeywords(((Player) player)) + " "
                        + ColourUtil.colourise(" you for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            } else if (forWhom == 0) {
                return replaceActionObserverKeywords(((Player) player)) + " "
                        + ColourUtil.colourise(target.getAliveName(), colourTarget)
                        + ColourUtil.colourise(" for ", typeColour)
                        + ColourUtil.colourise(dmg + "", ColourUtil.damage)
                        + ColourUtil.colourise(" damage!", typeColour);
            }
        }
        return "";
    }

    private String replaceActionKeywords(Player player) {
        String[] words = actionDesc.split(" ");

        for (int x = 0; x < words.length; x++) {
            if (words[x].equals("[YOU]")) {
                words[x] = ColourUtil.colourise("YOU", typeColour);
            } else if (words[x].equals("[YOUR]")) {
                words[x] = ColourUtil.colourise("YOUR", typeColour);
            } else if (words[x].equals("[you]")) {
                words[x] = ColourUtil.colourise("you", typeColour);
            } else if (words[x].equals("[your]")) {
                words[x] = ColourUtil.colourise("your", typeColour);
            } else if (words[x].equals("[You]")) {
                words[x] = ColourUtil.colourise("You", typeColour);
            } else if (words[x].equals("[Your]")) {
                words[x] = ColourUtil.colourise("Your", typeColour);
            } else if (words[x].equals("[PLAYER]")) {
                words[x] = ColourUtil.colourise(player.getAliveName(), ColourUtil.player);
            } else if (words[x].equals("[PLAYERS]")) {
                words[x] = ColourUtil.colourise(player.getAliveName() + "'s", ColourUtil.player);
            } else {
                words[x] = ColourUtil.colourise(words[x], typeColour);
            }
        }

        if (words.length > 0) {
            String output = words[0];

            for (int x = 1; x < words.length; x++) {
                output += " " + words[x];
            }

            return output;
        }

        return actionDesc;
    }

    private String replaceActionObserverKeywords(Player player) {
        String[] words = actionDesc.split(" ");

        for (int x = 0; x < words.length; x++) {
            if (words[x].equals("[YOU]")) {
                words[x] = ColourUtil.colourise(player.getAliveName().toUpperCase(), ColourUtil.player);
            } else if (words[x].equals("[YOUR]")) {
                words[x] = ColourUtil.colourise(player.getAliveName().toUpperCase() + "'s", ColourUtil.player);
            } else if (words[x].equals("[you]")) {
                words[x] = ColourUtil.colourise(player.getAliveName(), ColourUtil.player);
            } else if (words[x].equals("[your]")) {
                words[x] = ColourUtil.colourise(player.getAliveName() + "'s", ColourUtil.player);
            } else if (words[x].equals("[You]")) {
                words[x] = ColourUtil.colourise(player.getAliveName(), ColourUtil.player);
            } else if (words[x].equals("[Your]")) {
                words[x] = ColourUtil.colourise(player.getAliveName() + "'s", ColourUtil.player);
            } else if (words[x].equals("[PLAYER]")) {
                words[x] = ColourUtil.colourise(player.getAliveName(), ColourUtil.player);
            } else if (words[x].equals("[PLAYERS]")) {
                words[x] = ColourUtil.colourise(player.getAliveName() + "'s", ColourUtil.player);
            } else {
                words[x] = ColourUtil.colourise(words[x], typeColour);
            }
        }

        if (words.length > 0) {
            String output = words[0];

            for (int x = 1; x < words.length; x++) {
                output += " " + words[x];
            }

            return output;
        }

        return actionDesc;
    }

    private double attackerCrit(Fightable attacker, Fightable defender, boolean critical) {
        double critMod = 1;

        if (critical) {
            critMod = Math.max(1, 2 - ((double) defender.getHelmMod() / 100));
        }

        return critMod;
    }

    private boolean defenderEvade(Fightable attacker, Fightable defender) {
        // If not a critical, risk opponent evading.
        double roll = r.nextDouble();
        int speedDiff = defender.getSpeed() - attacker.getSpeed();
        boolean evadeSuccess = roll <= (double) ((defender.getSpeed() + speedDiff - (double) defender.getBootsMod() / 4)
                * (1 + ((double) defender.getLuck() / 100))) / 200;
        return evadeSuccess;
    }

    private int dealDamage(Fightable attacker, double critMod, Fightable defender) {
        int dmgDiff = 0;

        int bound;
        if (isDicePerLevel()) {
            bound = attacker.getLevel();
        } else {
            bound = dice;
        }

        System.out.println("Rolling dice.");
        // Roll the dice
        for (int x = 0; x < bound; x++) {
            dmgDiff += r.nextInt(diceSides) + 1;
        }

        // Add the attackers influencing stat
        dmgDiff += getMainStatVal(attacker);

        int dmgRcvd = Math.max(1,
                (int) (critMod * (attacker.getAttack() + dmgDiff))
                - defender.getChestMod() - defender.getBootsMod());

        if (!defender.isImmortal()) {
            defender.modifyHealth(-dmgRcvd);
        }

        System.out.println("Damage dealt.");

        return dmgRcvd;
    }

    private int getXPReward(Fightable attacker, Fightable defender) {
        int levelDiff = 10 + defender.getLevel() - attacker.getLevel();
        levelDiff = Math.max(levelDiff, 1);

        // Get attack/defence difference
        int statDiff = defender.getAttack() + defender.getDefence()
                - attacker.getAttack() - attacker.getDefence();
        statDiff = Math.max(statDiff, 1);

        int xpGained = levelDiff * statDiff;

        return xpGained;
    }

    private int getBonusXPReward(Fightable attacker, int xpGained, Fightable defender) {
        double bonusRoll = r.nextDouble();
        boolean bonusXP = bonusRoll <= ((double) attacker.getLuck() / 100);
        if (bonusXP) {
            xpGained = Math.max(1, (int) (xpGained * bonusRoll));
            return xpGained;
        }
        return 0;
    }

    private boolean sameLocation(Fightable attacker, Fightable defender) {
        if (attacker.x() == defender.x() && attacker.y() == defender.y()
                && attacker.z() == defender.z()) {
            return true;
        }
        return false;
    }

    private boolean stillAlive(Fightable attacker, Fightable defender) {
        if (attacker.isDead() || defender.isDead()) {
            return false;
        }
        return true;
    }

    @Override
    public String getSkillHelp() {
        String output = synonyms[0] + "\n---\n"
                + "Learned at level " + levelLearned
                + "\nUsage(s): " + usage
                + "\nDamage  : ";

        if (isDicePerLevel()) {
            output += "[LVL]d" + diceSides + " + [" + getMainStatName() + "]";
        }

        output += "\n" + description;

        return output;
    }
}
